const gobangState = {
  None: 0,
  Black: 1,
  White: 2,
} as const;

type GobangColor = (typeof gobangState)[keyof typeof gobangState];
type Coord = [number, number];
type MovementFn = (p1: Coord) => Coord;

function isValid(board: GobangColor[][], point: Coord, color: GobangColor) {
  const r = board.length;
  const c = board[0].length;
  const [x, y] = point;
  return x >= 0 && x < r && y >= 0 && y < c && board[x][y] === color;
}

/**
 * 创建一个判断函数，用于判断某个位置是否可以放置棋子
 * @param p1Movement 第一个方向上的移动函数
 * @param p2Movement 第一个方向上的移动函数
 * @returns  返回一个函数，该函数接受一个棋盘、一个坐标和一个颜色作为参数
 */
function createJudgement(p1Movement: MovementFn, p2Movement: MovementFn) {
  // 返回一个函数，该函数接受一个棋盘、一个坐标和一个颜色作为参数
  return function (board: GobangColor[][], point: Coord, color: GobangColor) {
    // 初始化计数器为1
    let count = 1;
    // 获取两个方向上的下一个位置
    let p1 = p1Movement(point),
      p2 = p2Movement(point);
    // 循环判断
    while (1) {
      // 初始化两个方向的标志位为false
      let p1Changed = false,
        p2Changed = false;
      // 判断第一个方向上的位置是否可以放置棋子
      if (isValid(board, p1, color)) {
        // 如果可以放置，计数器加1，并获取下一个位置
        count++;
        p1 = p1Movement(p1);
        // 设置第一个方向的标志位为true
        p1Changed = true;
      }
      // 判断第二个方向上的位置是否可以放置棋子
      if (isValid(board, p2, color)) {
        // 如果可以放置，计数器加1，并获取下一个位置
        count++;
        p2 = p2Movement(p2);
        // 设置第二个方向的标志位为true
        p2Changed = true;
      }
      // 如果计数器大于等于5，返回true
      if (count >= 5) {
        return true;
      }
      // 如果两个方向的标志位都为false，返回false
      if (!p1Changed && !p2Changed) {
        break;
      }
    }
    return false;
  };
}

const horJudgement = createJudgement(
  ([i, j]) => [i, j - 1],
  ([i, j]) => [i, j + 1]
);
const verJudgement = createJudgement(
  ([i, j]) => [i - 1, j],
  ([i, j]) => [i + 1, j]
);
const diaJudgement = createJudgement(
  ([i, j]) => [i - 1, j - 1],
  ([i, j]) => [i + 1, j + 1]
);
const antiDiaJudgement = createJudgement(
  ([i, j]) => [i + 1, j - 1],
  ([i, j]) => [i - 1, j + 1]
);

/**
 * 判断五子棋是否胜利
 * @param board 棋盘
 * @param point 当前落子位置
 * @param color 当前落子颜色
 * @returns
 */
export function gobangIsWin(
  // 棋盘
  board: GobangColor[][],
  // 当前落子位置
  point: Coord,
  // 当前落子颜色
  color: GobangColor
) {
  // 判断横向、纵向、斜向、反斜向是否有五个连续的相同颜色的棋子
  return (
    horJudgement(board, point, color) ||
    verJudgement(board, point, color) ||
    diaJudgement(board, point, color) ||
    antiDiaJudgement(board, point, color)
  );
}
